package org.chen.service;

import lombok.extern.slf4j.Slf4j;
import org.chen.dto.RateLimiterConfigDTO;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@Component
@Slf4j
public class DistributedRateLimiter {
    private static final String DOMAIN_LAST_REQUEST_KEY = "rate:domain:last:";
    private static final String DOMAIN_CONCURRENT_KEY = "rate:domain:concurrent:";
    private static final String GLOBAL_CONCURRENT_KEY = "rate:global:concurrent";
    private static final int KEY_EXPIRE_HOURS = 24;  // 设置24小时过期

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    // 使用AtomicInteger来支持动态修改
    private final AtomicInteger maxConcurrentPerDomain = new AtomicInteger(2);
    // 增加随机延迟
    private final AtomicInteger minInterval = new AtomicInteger(1000);
    // 增加随机延迟
    private final AtomicInteger maxInterval = new AtomicInteger(3000);
    // 同一域名请求间隔（毫秒）
    private final AtomicInteger domainInterval = new AtomicInteger(1500);
    //全局并发量
    private final AtomicInteger globalMaxConcurrent = new AtomicInteger(10);

    public void acquirePermit(String url) throws InterruptedException {
        String domain = extractDomain(url);

        // 获取全局信号量
        RSemaphore globalSemaphore = redissonClient.getSemaphore(GLOBAL_CONCURRENT_KEY);
        // 确保全局信号量存在
        if (!globalSemaphore.isExists()) {
            globalSemaphore.trySetPermits(globalMaxConcurrent.get());
            // 设置过期时间
            globalSemaphore.expire(KEY_EXPIRE_HOURS, TimeUnit.HOURS);
        }

        // 获取域名信号量
        RSemaphore domainSemaphore = redissonClient.getSemaphore(DOMAIN_CONCURRENT_KEY + domain);
        // 确保域名信号量存在
        if (!domainSemaphore.isExists()) {
            domainSemaphore.trySetPermits(maxConcurrentPerDomain.get());
            // 设置过期时间
            domainSemaphore.expire(KEY_EXPIRE_HOURS, TimeUnit.HOURS);
        }

        try {
            // 尝试获取全局许可
            if (!globalSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
                throw new InterruptedException("Failed to acquire global permit");
            }

            try {
                // 尝试获取域名许可
                if (!domainSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
                    throw new InterruptedException("Failed to acquire domain permit");
                }

                try {
                    // 检查域名访问间隔
                    checkAndWaitForInterval(domain);

                    // 添加随机延迟
                    addRandomDelay();

                } finally {
                    domainSemaphore.release();
                }
            } finally {
                globalSemaphore.release();
            }
        } catch (InterruptedException e) {
            log.error("Failed to acquire permit for domain: {}", domain, e);
            throw e;
        }
    }

    /**
     * 检查并等待域名访问间隔
     */
    private void checkAndWaitForInterval(String domain) throws InterruptedException {
        String lastRequestKey = DOMAIN_LAST_REQUEST_KEY + domain;
        String lastRequestTime = redisTemplate.opsForValue().get(lastRequestKey);

        if (lastRequestTime != null) {
            long lastRequest = Long.parseLong(lastRequestTime);
            long currentTime = System.currentTimeMillis();
            long waitTime = domainInterval.get() - (currentTime - lastRequest);

            if (waitTime > 0) {
                Thread.sleep(waitTime);
            }
        }

        // 更新最后请求时间
        redisTemplate.opsForValue().set(
                lastRequestKey,
                String.valueOf(System.currentTimeMillis()),
                30,  // 30分钟过期
                TimeUnit.MINUTES
        );
    }

    /**
     * 添加随机延迟
     */
    private void addRandomDelay() throws InterruptedException {
        long randomDelay = minInterval.get() +
                new Random().nextInt(maxInterval.get() - minInterval.get());
        Thread.sleep(Math.min(randomDelay, 1500));
    }

    /**
     * 提取域名
     */
    private String extractDomain(String url) {
        try {
            return new URL(url).getHost();
        } catch (MalformedURLException e) {
            return url;
        }
    }

    /**
     * 动态调整域名并发数
     */
    public void adjustDomainConcurrent(String domain, boolean success) {
        RSemaphore domainSemaphore = redissonClient.getSemaphore(DOMAIN_CONCURRENT_KEY + domain);

        try {
            if (success) {
                // 成功后增加许可
                int currentPermits = (int) domainSemaphore.availablePermits();
                if (currentPermits < globalMaxConcurrent.get()) {  // 最大允许3个并发
                    domainSemaphore.trySetPermits(currentPermits + 1);
                    // 重新设置过期时间
                    domainSemaphore.expire(KEY_EXPIRE_HOURS, TimeUnit.HOURS);
                }
            } else {
                // 失败后降低许可
                domainSemaphore.trySetPermits(1);
                // 重新设置过期时间
                domainSemaphore.expire(KEY_EXPIRE_HOURS, TimeUnit.HOURS);
            }
        } catch (Exception e) {
            log.error("Failed to adjust domain concurrent for: {}", domain, e);
        }
    }

    /**
     * 更新配置
     */
    public void updateConfig(RateLimiterConfigDTO config) {
        if (config.getMaxConcurrentPerDomain() != null) {
            maxConcurrentPerDomain.set(config.getMaxConcurrentPerDomain());
        }
        if (config.getGlobalConcurrent() != null) {
            updateGlobalConcurrent(config.getGlobalConcurrent());
        }
        if (config.getMinInterval() != null) {
            minInterval.set(config.getMinInterval());
        }
        if (config.getMaxInterval() != null) {
            maxInterval.set(config.getMaxInterval());
        }
        if (config.getDomainInterval() != null) {
            domainInterval.set(config.getDomainInterval());
        }
    }

    /**
     * 更新全局并发数
     */
    private void updateGlobalConcurrent(int newConcurrent) {
        RSemaphore globalSemaphore = redissonClient.getSemaphore(GLOBAL_CONCURRENT_KEY);
        try {
            globalSemaphore.trySetPermits(newConcurrent);
            globalMaxConcurrent.set(newConcurrent);
            // 重新设置过期时间
            globalSemaphore.expire(KEY_EXPIRE_HOURS, TimeUnit.HOURS);
        } catch (Exception e) {
            log.error("Failed to update global concurrent", e);
        }
    }
}